﻿/******************************************************************************
	Cobalt - utility libraries for building XNA games
	Copyright (C) 2008 Philip Djonov

	This program is free software; you can redistribute it and/or modify it
	under the terms of the GNU General Public License as published by the Free
	Software Foundation; either version 2 of the License, or (at your option)
	any later version.

	This program is distributed in the hope that it will be useful, but
	WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
	or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
	for more details.

	You should have received a copy of the GNU General Public License along
	with this program; if not, write to the
	
		Free Software Foundation, Inc.
		51 Franklin Street, Fifth Floor
		Boston, MA  02110-1301, USA.
******************************************************************************/

using System;
using System.Collections.Generic;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace Cobalt.Framework.UI
{
	using Graphics;

	public class UiContext : DrawableGameComponent
	{
		public UiContext( Game game )
			: base( game )
		{
			renderTraverseCtx = new RenderTraversalContext( this );
			FocusedPath = new MutableRoutingPath();

			frameworkContent = new ContentManager( game.Services, "Content\\Cobalt.Framework" );

			textLayout = new TextLayout();
			textBuffer = new TextBuffer( 1024 );
		}

		#region Content

		private SpriteBatch spriteBatch;

		private ContentManager frameworkContent;
		internal ContentManager FrameworkContent { get { return frameworkContent; } }

		private Font defaultFont;
		public Font DefaultFont { get { return defaultFont; } }

		private Texture2D whiteImage;
		public Texture2D WhiteImage { get { return whiteImage; } }

		private SpriteSheet defaultSprites;
		public SpriteSheet DefaultSprites { get { return defaultSprites; } }

		private TextLayout textLayout;
		public TextLayout TextLayout { get { return textLayout; } }

		private TextBuffer textBuffer;
		public TextBuffer TextBuffer { get { return textBuffer; } }

		private Rectangle screenBounds;
		public Rectangle ScreenBounds { get { return screenBounds; } }

		protected override void LoadContent()
		{
			base.LoadContent();

			spriteBatch = new SpriteBatch( GraphicsDevice );

			defaultFont = frameworkContent.Load<Font>( "DefaultFont" );
			whiteImage = frameworkContent.Load<Texture2D>( "White" );

			defaultSprites = frameworkContent.Load<SpriteSheet>( "DefaultSprites" );

			screenBounds = GraphicsDevice.Viewport.TitleSafeArea;
		}

		protected override void UnloadContent()
		{
			Helpers.DisposeAndNull( ref spriteBatch );

			defaultSprites = null;

			defaultFont = null;
			whiteImage = null;

			frameworkContent.Unload();

			base.UnloadContent();
		}

		#endregion

		private List<Element> elements = new List<Element>();

		public void Show( Element element )
		{
			if( element == null )
				throw new ArgumentNullException();

			int idx = elements.IndexOf( element );

			RootState rs = new RootState();
			rs.IsVisible = true;

			if( idx >= 0 )
			{
				if( idx == elements.Count - 1 )
					return;

				FocusedPath.Clear();

				elements.RemoveAt( idx );

				//tell the old topmost element that it's
				//about to not be such any longer
				rs.IsTopmost = false;
				elements[elements.Count - 1].OnRootStateChanged( rs );
			}

			FocusedPath.Clear();

			elements.Add( element );

			//tell element that it's becoming topmost
			rs.IsTopmost = true;
			element.OnRootStateChanged( rs );
		}

		public void Hide( Element element )
		{
			if( element == null )
				throw new ArgumentNullException();

			if( elements.Remove( element ) )
			{
				if( FocusedPath.Contains( element ) )
					FocusedPath.Clear();

				RootState rs = new RootState();

				rs.IsTopmost = false;
				rs.IsVisible = false;

				element.OnRootStateChanged( rs );

				if( elements.Count > 0 )
				{
					rs.IsVisible = true;
					rs.IsTopmost = true;

					elements[elements.Count - 1].OnRootStateChanged( rs );
				}
			}
		}

		public bool IsVisible( Element element )
		{
			if( element == null )
				throw new ArgumentNullException();

			return elements.Contains( element );
		}

		#region ActiveRoute

		public IRoute ActiveRoute { get { return activeRoutes.Count != 0 ? activeRoutes.Peek() : emptyRoute; } }

		private Stack<IRoute> activeRoutes = new Stack<IRoute>();
		private IRoute emptyRoute = new EmptyRoute();

		public void PushTemporaryRoute( IRoute route )
		{
			activeRoutes.Push( route );
		}

		public void PopTemporaryRoute()
		{
			activeRoutes.Pop();
		}

		private class EmptyRoute : IRoute
		{
			private RoutingPath path = new RoutingPath();

			#region IRoute Members

			public RoutingPath Path
			{
				get { return path; }
			}

			#endregion
		}

		internal object GetRoutingMeta( Element element )
		{
			RoutingPath path = ActiveRoute.Path;
			for( int i = path.Count; i-- != 0; )
				if( path[i].Element == element )
					return path[i].Meta;

			return null;
		}

		#endregion

		#region Render

		private sealed class RenderTraversalContext : ITraversalContext, IRenderContext
		{
			private UiContext owner;

			private Vector2 origin;
			private float opacity;

			private MutableRoutingPath stack = new MutableRoutingPath();

			public RoutingPath Path { get { return stack; } }

			private SpriteBatch spriteBatch;

			internal RenderTraversalContext( UiContext owner )
			{
				this.owner = owner;
			}

			public void Render( Element element, SpriteBatch batch )
			{
				this.spriteBatch = batch;

				batch.Begin( SpriteBlendMode.AlphaBlend, SpriteSortMode.Immediate, SaveStateMode.None );

				this.origin = Vector2.Zero;
				this.opacity = 1.0F;

				Traverse( element, null );

				batch.End();
			}

			#region ITraversalContext Members

			public void Traverse( Element element, object meta )
			{
				stack.Push( element, meta );

				if( !element.Visible.Evaluate() )
				{
					stack.Pop();
					return;
				}

				float op = element.Opacity.Evaluate();
				if( op <= 0 )
				{
					stack.Pop();
					return;
				}

				Vector2 or = element.Origin.Evaluate();

				origin += or;
				opacity *= op;

				element.Render( this );
				element.TraverseToRender( this );

				stack.Pop();

				switch( stack.Count )
				{
				case 0:
					origin = Vector2.Zero;
					opacity = 1.0F;
					break;

				/*
				case 1:
					origin = stack.Peek().Element.Origin.Evaluate();
					opacity = stack.Peek().Element.Opacity.Evaluate();
					break;
				*/

				default:
					origin -= or;
					opacity /= op;
					break;
				}
			}

			#endregion

			#region IRenderContext Members

			public void Draw( Texture2D texture, Vector2 position, Rectangle? sourceRectangle,
				Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects )
			{
				position.X += this.origin.X;
				position.Y += this.origin.Y;

				if( opacity != 1.0F )
				{
					color.A = (byte)(color.A * opacity);
				}

				spriteBatch.Draw( texture, position, sourceRectangle, color, rotation, origin, scale, effects, 0 );
			}

			#endregion
		}

		private RenderTraversalContext renderTraverseCtx;

		public override void Draw( GameTime gameTime )
		{
			activeRoutes.Push( renderTraverseCtx );

			for( int i = 0; i < elements.Count; i++ )
				renderTraverseCtx.Render( elements[i], spriteBatch );

			activeRoutes.Pop();	
		}

		#endregion

		#region Event Routing
		class EventRoutingContext : IEventRoutingContext
		{
			public EventRoutingContext()
			{
				this.path = new RoutingPath();
			}

			public bool Route( Event ev, IList<TraverseEntry> routePath )
			{
				this.ev = ev;
				this.handled = false;

				path.Begin( routePath );

				for( int i = 0; i < routePath.Count; i++ )
				{
					TraverseEntry elem = routePath[i];

					path.Descend();
					ev.RouteInto( this );

					elem.Element.OnPreviewEvent( this );
				}

				for( int i = routePath.Count; i-- != 0; )
				{
					TraverseEntry elem = routePath[i];

					elem.Element.OnHandleEvent( this );

					ev.RoutOutOf( this );
					path.Ascend();
				}

				return handled;
			}

			#region IRoutingContext Members

			private Event ev;
			public Event Event
			{
				get { return ev; }
			}

			private bool handled;
			public bool IsHandled { get { return handled; } }

			public void MarkHandled()
			{
				handled = true;
			}

			#endregion

			#region IRoute Members

			private RoutingPath path;
			public RoutingPath Path
			{
				get { return path; }
			}

			#endregion
		}

		private EventRoutingContext eventRouteCtx = new EventRoutingContext();

		public class RouteEventArgs : EventArgs
		{
			public Event Event { get; internal set; }
			public RoutingPath Route { get; internal set; }

			public bool Cancel { get; set; }

			internal RouteEventArgs()
			{
			}
		}

		public delegate void RouteEventHandler( object sender, RouteEventArgs e );

		public event RouteEventHandler BeforeRoute;

		protected virtual void OnBeforeRoute( RouteEventArgs e )
		{
			if( BeforeRoute != null )
				BeforeRoute( this, e );
		}

		private RouteEventArgs routeEvArgs = new RouteEventArgs();

		public bool Route( Event ev, RoutingPath route )
		{
			routeEvArgs.Event = ev;
			routeEvArgs.Route = route;
			routeEvArgs.Cancel = false;

			OnBeforeRoute( routeEvArgs );

			if( routeEvArgs.Cancel )
				return false;

			activeRoutes.Push( eventRouteCtx );

			bool ret = eventRouteCtx.Route( ev, route );

			activeRoutes.Pop();

			return ret;
		}
		#endregion

		#region Input

		public MutableRoutingPath FocusedPath { get; private set; }

		public bool IsFocusedElement( Element element )
		{
			var activePath = ActiveRoute.Path;
			var focusPath = FocusedPath;

			//if it isn't focused, well, duh?

			if( focusPath.Top.Element != element )
				return false;

			//the active path doesn't even contain the focused path

			if( focusPath.Count > activePath.Count )
				return false;

			//see if the focused path is identical to the active path

			for( int i = 0; i < focusPath.Count; i++ )
			{
				if( focusPath[i] != activePath[i] )
					return false;
			}

			return true;
		}

		public bool IsOrContainsFocusedElement( Element element )
		{
			var activePath = ActiveRoute.Path;
			var focusPath = FocusedPath;

			//see if the focused path is identical to the active path

			int limit = Math.Min( activePath.Count, focusPath.Count );
			for( int i = 0; i < limit; i++ )
			{
				if( focusPath[i] != activePath[i] )
					//focus path wanders off
					return false;

				if( focusPath[i].Element == element )
					//awesome, found the element somewhere on the focus path
					return true;
			}

			return false;
		}

		private List<InputProvider> inputProviders = new List<InputProvider>();
		public void RegisterInputProvider( InputProvider provider )
		{
			if( provider == null )
				throw new ArgumentNullException();
			if( provider.Owner != null )
				throw new ArgumentException();

			provider.Owner = this;
			inputProviders.Add( provider );
		}

		#endregion

		private TimeSpan now;
		/// <summary>
		/// Gets the current time.
		/// </summary>
		public TimeSpan Now { get { return now; } }

		public override void Update( GameTime gameTime )
		{
			base.Update( gameTime );

			this.now = gameTime.TotalRealTime;

			for( int i = 0; i < inputProviders.Count; i++ )
				inputProviders[i].Tick();
		}
	}

	#region IRoute

	public interface IRoute
	{
		RoutingPath Path { get; }
	}

	public class RoutingPath : IList<TraverseEntry>
	{
		private IList<TraverseEntry> path;
		private int top;

		public TraverseEntry Top
		{
			get { return path[top - 1]; }
		}

		#region Internal interface

		internal void Begin( IList<TraverseEntry> path )
		{
			this.path = path;
			this.top = 0;
		}

		internal void Descend()
		{
			top++;
		}

		internal void Ascend()
		{
			top--;
		}

		internal void End()
		{
			this.path = null;
		}

		#endregion

		#region IList<TraverseEntry> Members

		public int IndexOf( TraverseEntry item )
		{
			int index = path.IndexOf( item );
			
			if( index < 0 || index >= top )
				return -1;

			return index;
		}

		void IList<TraverseEntry>.Insert( int index, TraverseEntry item )
		{
			throw new NotSupportedException();
		}

		void IList<TraverseEntry>.RemoveAt( int index )
		{
			throw new NotSupportedException();
		}

		TraverseEntry IList<TraverseEntry>.this[int index]
		{
			get
			{
				if( index < 0 || index >= top )
					throw new IndexOutOfRangeException();

				return path[index];
			}
			set
			{
				throw new NotSupportedException();
			}
		}

		public TraverseEntry this[int index]
		{
			get
			{
				if( index < 0 || index >= top )
					throw new IndexOutOfRangeException();

				return path[index];
			}
		}

		#endregion

		#region ICollection<TraverseEntry> Members

		void ICollection<TraverseEntry>.Add( TraverseEntry item )
		{
			throw new NotSupportedException();
		}

		void ICollection<TraverseEntry>.Clear()
		{
			throw new NotSupportedException();
		}

		public bool Contains( TraverseEntry item )
		{
			return IndexOf( item ) != -1;
		}

		public bool Contains( Element element )
		{
			for( int i = 0; i < top; i++ )
			{
				if( path[i].Element == element )
					return true;
			}

			return false;
		}

		void ICollection<TraverseEntry>.CopyTo( TraverseEntry[] array, int arrayIndex )
		{
			if( array == null )
				throw new ArgumentNullException();
			if( arrayIndex < 0 || arrayIndex + top > array.Length )
				throw new IndexOutOfRangeException();

			for( int i = 0; i < top; i++ )
				array[arrayIndex + i] = path[i];
		}

		public int Count
		{
			get { return top; }
		}

		bool ICollection<TraverseEntry>.IsReadOnly
		{
			get { return true; }
		}

		bool ICollection<TraverseEntry>.Remove( TraverseEntry item )
		{
			throw new NotSupportedException();
		}

		#endregion

		#region Enumerable

		public struct Enumerator : IEnumerator<TraverseEntry>
		{
			private RoutingPath path;
			private int index;

			internal Enumerator( RoutingPath path )
			{
				this.path = path;
				this.index = -1;
			}

			#region IEnumerator<TraverseEntry> Members

			public TraverseEntry Current
			{
				get
				{
					if( index < 0 || index >= path.Count )
						throw new InvalidOperationException();

					return path[index];
				}
			}

			#endregion

			#region IDisposable Members

			public void Dispose()
			{
			}

			#endregion

			#region IEnumerator Members

			object System.Collections.IEnumerator.Current
			{
				get { return Current; }
			}

			public bool MoveNext()
			{
				if( index == path.Count )
					return false;

				index++;
				return true;
			}

			public void Reset()
			{
				index = -1;
			}

			#endregion
		}

		public Enumerator GetEnumerator()
		{
			return new Enumerator( this );
		}

		#region IEnumerable<TraverseEntry> Members

		IEnumerator<TraverseEntry> IEnumerable<TraverseEntry>.GetEnumerator()
		{
			return GetEnumerator();
		}

		#endregion

		#region IEnumerable Members

		System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
		{
			return GetEnumerator();
		}

		#endregion

		#endregion
	}

	public class MutableRoutingPath : RoutingPath, IRoute
	{
		private List<TraverseEntry> pathElems = new List<TraverseEntry>( 64 );

		public MutableRoutingPath()
		{
			Begin( pathElems );
		}

		public void Push( Element element, object meta )
		{
			pathElems.Add( new TraverseEntry( element, meta ) );
			Descend();
		}

		public void Push( Element element )
		{
			Push( element, null );
		}

		public void Pop()
		{
			Ascend();
			pathElems.RemoveAt( pathElems.Count - 1 );
		}

		public void PopUpTo( Element element )
		{
			while( Count > 0 && Top.Element != element )
				Pop();
		}

		public void Clear()
		{
			End();
			pathElems.Clear();
			Begin( pathElems );
		}

		#region IRoute Members

		RoutingPath IRoute.Path
		{
			get { return this;  }
		}

		#endregion
	}

	#endregion

	#region IRenderContext

	public interface IRenderContext : IRoute
	{
		void Draw( Texture2D texture, Vector2 position, Rectangle? sourceRectangle, Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects );
	}

	public static class IRenderContextHelpers
	{
		public static void Draw( this IRenderContext ctx, Texture2D texture, Vector2 position )
		{
			ctx.Draw( texture, position, null, Color.White, 0, new Vector2(), new Vector2( 1, 1 ), SpriteEffects.None );
		}

		public static void Draw( this IRenderContext ctx, Texture2D texture, Vector2 position, Color color )
		{
			ctx.Draw( texture, position, null, color, 0, new Vector2(), new Vector2( 1, 1 ), SpriteEffects.None );
		}

		public static void Draw( this IRenderContext ctx, SpriteSheet sheet, int spriteIndex, Vector2 position,
			Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects )
		{
			Texture2D tex;
			Rectangle sourceRect;

			sheet.GetSpriteInfo( spriteIndex, out tex, out sourceRect );

			ctx.Draw( tex, position, sourceRect, color, rotation, origin, scale, effects );
		}

		public static void Draw( this IRenderContext ctx, SpriteSheet sheet, int spriteIndex, Vector2 position )
		{
			Texture2D tex;
			Rectangle sourceRect;

			sheet.GetSpriteInfo( spriteIndex, out tex, out sourceRect );

			ctx.Draw( tex, position, sourceRect, Color.White, 0, new Vector2(), new Vector2( 1, 1 ), SpriteEffects.None );
		}

		public static void Draw( this IRenderContext ctx, SpriteSheet sheet, int spriteIndex, Vector2 position, Color color )
		{
			Texture2D tex;
			Rectangle sourceRect;

			sheet.GetSpriteInfo( spriteIndex, out tex, out sourceRect );

			ctx.Draw( tex, position, sourceRect, color, 0, new Vector2(), new Vector2( 1, 1 ), SpriteEffects.None );
		}

		public static void Draw( this IRenderContext ctx, SpriteSheet sheet, string spriteName, Vector2 position,
			Color color, float rotation, Vector2 origin, Vector2 scale, SpriteEffects effects )
		{
			Texture2D tex;
			Rectangle sourceRect;

			sheet.GetSpriteInfo( spriteName, out tex, out sourceRect );

			ctx.Draw( tex, position, sourceRect, color, rotation, origin, scale, effects );
		}

		public static void Draw( this IRenderContext ctx, SpriteSheet sheet, string spriteName, Vector2 position )
		{
			Texture2D tex;
			Rectangle sourceRect;

			sheet.GetSpriteInfo( spriteName, out tex, out sourceRect );

			ctx.Draw( tex, position, sourceRect, Color.White, 0, new Vector2(), new Vector2( 1, 1 ), SpriteEffects.None );
		}

		public static void Draw( this IRenderContext ctx, SpriteSheet sheet, string spriteName, Vector2 position, Color color )
		{
			Texture2D tex;
			Rectangle sourceRect;

			sheet.GetSpriteInfo( spriteName, out tex, out sourceRect );

			ctx.Draw( tex, position, sourceRect, color, 0, new Vector2(), new Vector2( 1, 1 ), SpriteEffects.None );
		}
	}

	#endregion

	#region ITraversalContext

	public interface ITraversalContext : IRoute
	{
		void Traverse( Element element, object meta );
	}

	public struct TraverseEntry
	{
		private Element element;
		private object meta;

		public Element Element { get { return element; } }
		public object Meta { get { return meta; } }

		public TraverseEntry( Element element, object meta )
		{
			this.element = element;
			this.meta = meta;
		}

		public static bool operator ==( TraverseEntry a, TraverseEntry b )
		{
			return a.element == b.element &&
				a.meta == b.meta;
		}

		public static bool operator !=( TraverseEntry a, TraverseEntry b )
		{
			return a.element != b.element ||
				a.meta != b.meta;
		}

		public bool Equals( TraverseEntry other )
		{
			return element == other.element &&
				meta == other.meta;
		}

		public override bool Equals( object obj )
		{
			if( obj == null || obj.GetType() != typeof( TraverseEntry ) )
				return false;

			return Equals( (TraverseEntry)obj );
		}
	}

	public static class ITraversalContextHelpers
	{
		public static void Traverse( this ITraversalContext ctx, Element element )
		{
			ctx.Traverse( element, null );
		}
	}

	#endregion

	public interface IEventRoutingContext : IRoute
	{
		Event Event { get; }
		bool IsHandled { get; }

		void MarkHandled();
	}

	public abstract class InputProvider
	{
		public UiContext Owner { get; internal set; }

		public virtual void Tick()
		{
		}
	}

}